Apple, the Apple logo, AppleScript, Bento, Macintosh, QuickTime, and OpenDoc are
registered trademarks of Apple Computer, Inc.
Finder, Mac, and QuickDraw are trademarks of Apple Computer, Inc.
SOM, SOMObjects, and System Object Model are licensed trademarks of IBM Corporation.
About Linking Recipes
This document contains a number of recipes for creating and maintaining links between content in OpenDoc™ parts. It assumes familiarity with the document “Basic Data Interchange”, and references coding examples there. It also assumes you've read the “Clipboard Recipes” document; the basic clipboard recipes are augmented here for content kinds that support linking. Refer to the Class Documentation for a description of individual methods.
WARNING: CODE APPEARING IN THESE RECIPES HAS NOT BEEN TESTED, AND MAY CONTAIN ERRORS.
Summary of changes from DR4 Recipes
• Clarified that the Lock method of ODLink and ODLinkSource methods does not currently wait if the lock cannot be granted. See the section “Accessing a Link” in Linking Recipes Part 2.
Summary of changes from DR3 Recipes
• If your part calls RegisterDependent when the draft is read-only, your part's LinkUpdated method won't be called. See the section “Registering for Update Notification”.
• The GetContentStorageUnit method of ODLink will return the exception kODErrNoLinkContent if updating the source of the link failed. This ensures that the destination cannot attempt to embed a storage unit without content as a part. It may also return the exception kODErrCannotEstablishLink if a link cannot be established after your draft’s AcquireLink method returns a link object. See the section “Updating the Destination of a Link”.
• Parts can now use the Clear method to replace promises in a link without notifying destinations of an update. See the section “When CreateLink is called to get an existing link”.
• Notes on implementing undo of Paste (or Drop) have been added to Part 3.
• When cloning ODLink and ODLinkSource objects, parts must specify a destination storage unit of kODNULLID, not a specific storage unit. See the section “A Note About Cloning Linked Content”.
• A property (kODCloneKindUsed) and value (kODCloneKind) have been defined to indicate the clone kind value to use when fulfilling a promise. See the sections “Basic Recipe for Writing to a Link” and “Creating or Updating a Link Consisting of a Single Embedded Frame”. Parts can write this property instead of writing a clone kind into each promised value.
• The recipe for creating a manually-updated destination has been modified to deal with the fact that cross-document links may be created asynchronously. See the sections “Implementing Paste with Link” and “Registering for Update Notification”.
• All parts must validate the link status of each embedded frame they internalize, to ensure that the embedded frame’s link status is correct after part translation or rebinding to a different editor. See the section “Frame Link Status”.
• An alternative recipe for implementing undo of the creation of a link source is provided. Your part can use this recipe when a link source is created via drag and drop to ensure that there is a single undo item encompassing both the drop and the link creation. See the section “Better Undo Recipe for CreateLink”.
• The sections “Content Change Protocol” and “Implementing EmbeddedFrameUpdated” erroneously stated that a part’s implementation of EmbeddedFrameUpdated must pass the notification along by calling the ContentUpdated method of its affected display frames. Parts should not pass this notification along. The frame method ContentUpdated notifies all containing parts in the frame hierarchy.
• The example routine MyUpdateLinkDestination has been corrected to use a new update ID when propagating a manual link update to affected source links and containing frames.
• Under the heading “Implementing Paste with Link “, added mention that a part should not call the clipboard’s ActionDone method when pasting a link from the clipboard.
Summary of changes from DR2 Recipes
The differences between these recipes and those that accompanied DR2 are summarized here.
• The implementation of Clear has been changed to simplify part recipes. ODLinkSource::Clear now removes the contents property (and hence all values). After calling clear, your part must add the content property and write promise values (or actual values) into the link, just as when the link was created. After your part calls Unlock, OpenDoc will fulfill any promises for values actually in use. The section "Updating a Link" contains a new part recipe that includes this change.
• The section "When CreateLink is called to get an existing link" has been added to describe what a part should do when a second link destination is created via a link spec.
• The section "Specifying Kinds Supported via 'nmap' Resources" has been added.
• The section "Incorporating Linked Content the Efficient Way" has been revised to use the new draft method IsValidID, eliminating the need to catch exceptions in case AcquireLink or AcquireLinkSource fails.
• The rules for when a part should not write a link specification have been clarified.
• Recipes were structured so that if Unlock returned an error, Unlock would be called again during recovery from the error. They have been modified so Unlock is only called once, after any recovery from other errors has completed.
• Recipes were updated to conform to API changes in DR3:
- Calls to BeginClone include the destFrame argument.
- References to XXXChanged methods changed to XXXXUpdated as appropriate.
- ODChangeID replaced by ODUpdateID.
- IncRefCount replaced by Acquire.
- GetLink replaced by AcquireLink.
• The recipes for creating and updating a link have been rewritten to clarify their structure, and to demonstrate promising values in the link. A basic routine for writing to a link is used for both creating and updating a link.
• The recipe "Implementing Paste with Link" has been revised to show how a part is expected to handle undo history.
• The recipe "Implementing CreateLink" has been revised to include writing a shape and name property into the link, in case the contents of the link is embedded at the destination. It also demonstrates adding an undo action.
• The recipe "Updating the Destination of a Link" has been rewritten, and now shows how to deal with a link that has no content due to an error updating at the source of the link.
• Added a discussion of how parts at the source and destination of a link should handle the movement of the source content across parts.
• Undo of actions involving links is discussed.
• A discussion of "Copying a Single Embedded Frame at the Source or Destination of a Link" has been added.
• Parts need to remove their link spec from the clipboard when their ReleaseAll() method is called. This reverses an erroneous recipe change appearing in the DR2 recipes.
Summary of changes from November 1994 (DR1) Recipes
The differences between these recipes and those that accompanied DR1 include the above plus those summarized here.
• Parts should not write a link spec, nor allow a link to be pasted, if the draft permissions don't allow writing.
• The part method CreateLink, and the draft method CreateLinkSpec, have been changed to take an ODByteArray for compatibility with DSOM.
• RevealLink should make the document process frontmost, if necessary.
• Parts maintaining the destination of a link need to record the type to translate from if translation is performed. Also, the recipe "Implementing Paste with Link (Embedding Content)" now discusses applying a translation to the link destination.
• For future compatibility, in the recipes "Updating a Link" and "Updating the Destination of a Link", the part performing the update should not assume the link content storage unit is in the same draft as the part.
• The part method CreateLink was changed to return an ODLinkSource object, instead of an ODLink object. If your part doesn't support linking, all you need do is change the signature of your CreateLink method, and perhaps change your cast of kODNULL from ODLink to ODLinkSource. If your part supports linking, you probably have code near the end of CreateLink like:
CreateLink(...)
{
...
ODLink* link = linkSource->GetLink(ev);
link->IncrementRefCount(ev);
return link;
}
You should replace it with code like this:
CreateLink(...)
{
...
linkSource->IncrementRefCount(ev);
return linkSource;
}
This example assumes your part has stored a reference to the link source object returned by ODDraft::CreateLinkSource(), and hence must increment the ref count to account for the returned reference.
• Parts should no longer call the GetLink() method of ODLinkSource objects.
• The link info dialog methods, ODLink::ShowLinkDestinationInfo and ODLinkSource::ShowLinkSourceInfo, take an additional parameter, changesAllowed. If changesAllowed is kODFalse, the settings in the dialog can't be changed, and links cannot be broken or updated. (Note: recipes for calling these methods do not yet appear in this document, but the change is noted here for convenience.)
• When a single embedded frame is linked, and subsequently is cut or copied to the clipboard, a kODPropProxyContent property should be written by the containing part to hold the link or linkSource object. See "Links Containing Embedded Frames" for details.
• The recipe for removing a link spec no longer recommends removing the link spec when content copied to the clipboard is changed. It not unreasonable for different content to appear if the user pastes a link instead of performing a normal paste. The link spec should only be removed when it becomes infeasible to create the link.
• The size field has been removed from the ODLinkInfo structure, since it cannot be accurately determined.
• Protocol has been added to support navigation to the source of a link when the user attempts to edit a part embedded in a link destination. See "Editing in a Link Destination".
• When cloning a link or link source object, parts must check the object id returned by the draft's Clone method. The result will be a null object id if the clone is into a link. See "Notes on CloneInto When Called to Write to a Link".
• Parts no longer need to remove their link spec from the clipboard when their ReleaseAll() method is called. The Clipboard now performs this automatically.
• The sections “Implementing CreateLink” and “Updating a Link” have changed to describe how a part can use promises to provide only the data kind(s) used by destinations of the link.
• Added the section “Implementing Externalize” to point out the need to write persistent references to link, least they fall prey to garbage collection.
• The recipe for internalizing linked content has been expanded to two alternative recipes. The Easy Way demonstrates the simplest method of internalizing the content. The Efficient Way demonstrates a method that minimizes copying of data but may be a little less convenient to implement.
Summary of changes from a6 Recipes
The differences between these recipes and those that accompanied a6 are the above plus those summarized here.
• Conversion to SOM.
• When cloning to or from a link, use the clone kind constants kODCloneToLink or kODCloneFromLink.
• The recipe for internalizing a link has changed.
Linking Recipes
Header Files
Parts that support linking need to include three header files:
#ifndef SOM_ODLinkSource_xh
#include <LinkSrc.xh>
#endif
#ifndef SOM_ODLink_xh
#include <Link.xh>
#endif
#ifndef SOM_ODLinkSpec_xh
#include <LinkSpec.xh>
#endif
Utility Functions Used in Code Samples
To hide the details of passing ODByteArray parameters to ODStorageUnit methods, the code samples use the following utility functions:
StorageUnitSetValue(ODStorageUnit* su, Environment* ev, ODULong size, ODPtr buffer);
StorageUnitGetValue(ODStorageUnit* su, Environment* ev, ODULong size, ODPtr buffer);
These functions wrap their arguments into an ODByteArray and make the call to ODStorageUnit::SetValue or ODStorageUnit::GetValue.
Associating Links with Content
Parts are responsible for maintaining the association between link and link source objects and the linked content. The association is inherently part-specific, as it depends on the content model of a part.
Persistent Representation of Links
Because the information necessary to associate a link with a region of content is part specific, there is no universal standard for representing links in part content. However, all link representations need to include supplementary information in addition to a reference to the link or link source object.
For a source of a link, the update ID associated with the linked content should also be saved. This update ID may be different from the update ID of the link source object if the link is updated manually.
For a destination of a link, the supplementary information includes the fields from the ODLinkInfo structure: the creation and modification time, the update ID of the last content incorporated from the link, the autoUpdate setting, and the kind of content accessed from the link. In addition, if a translation is applied to the data at the destination, both the original kind and the kind translated into must be identified; the kind field of the ODLinkInfo structure should identify the destination kind, including any translation.
The format of a content kind defines the standard representation for links. This allows linked content to be transferred to another part that understands the same content kind, while maintaining the same characteristics as the original.
Implementing InitPartFromStorage
If a part supports linked content, its InitPartFromStorage method needs to verify persistent references to link source and link storage units, and ensure link source objects always identify the correct source part.
Even though parts strongly reference link and link source storage units, these references can become invalid during data interchange to maintain the link behavior users expect. InitPartFromStorage should use the IsValidStorageUnitRef method of the ODStorageUnit object to validate each link and link source reference before attempting to internalize the reference. If the reference is invalid, the part should eliminate the source or destination link from its content model, without alerting the user.
When a part internalizes a valid link source reference, it should call ODLinkSource::SetSourcePart to ensure that the object identifies the part maintaining the source of the link. The part owning the source content of the link will change if that content is moved to another part.
Parts typically do not register for update notification in their InitPartFromStorage method, although they may. See the section “Registering for Update Notification” for recommendations on when to register.
AcquireLinkFromFocusedSU reads a link reference, validates the reference, and internalizes the link object. The storage unit parameter must be focused to a reference to a link storage unit. If this routine returns kODNULL, the caller should remove the reference from the part’s content; this is part specific. If this routine returns a non-null result, the caller is responsible for releasing the object. Note: Because this routine internalizes a link object, it must not be called during a clone transaction.
link = su->GetDraft(ev)->AcquireLink(ev, linkID, kODNULL);
}
SOM_CATCH_ALL
SOM_ENDTRY
}
AcquireLinkSourceFromFocusedSU is very simlar to the preceeding routine. In addition, it ensures that the link source object references the right part. Note: Because this routine internalizes a link object, it must not be called during a clone transaction.
In your part’s Externalize method, ensure that the part’s storage unit (or one it references) contains a persistent reference to the link and link source objects your part uses. Even though your part holds a reference to the internalized link or link source object, container suite objects are subject to garbage collection when a draft is saved. If there are no persistent references to an object when the draft is saved, the underlying container suite object can be garbage collected even though the object has been internalized and has a non-zero reference count. This should never be a problem for production parts, but as you’re implementing linking be sure to write the externalization code early to avoid this potential problem.
A Note About Cloning Linked Content
If your part supports linked content, it will need to clone link and link source objects. Example code in this document uses the MyCloneStrongReference routines described in the Data Interchange Basics document. Note that these routines do not specify a particular destination storage unit to clone into. In fact, whenever your part clones a link or link source object, it must not specify a specific storage unit ID to clone into. Your part must clone link or link source objects by specifying kODNULLID as the destination storage unit.
Incorporating Linked Content from the Clipboard
Content on the clipboard may contain links. When that content is incorporated, any links should be incorporated, too. The representation of links is dependent on the content kind. However, all parts need to validate references to link and link source objects before using them, and must ensure that link source objects reference the part maintaining the source content.
This document shows two approaches for incorporating linked content. The first way is the easiest, so it’s attractive when you’re just getting linking working. The disadvantage of the first way is that it copies more data than necessary, potentially a lot more. Eventually, you’ll probably want to switch to the second recipe. It requires a little more work to implement, but can be significantly more efficient.
Incorporating Linked Content the Easy Way
The simplest way to incorporate linked content is to clone the content storage unit into your draft, extract the data format you want and incorporate it, and release the cloned storage unit. It leaves one or more abandoned storage units in your draft that will need to be garbage collected, but it’s straightforward to implement.
The example described below is an elaboration on the MyReadFromContentSU method described in the Data Interchange Basics document. Changes in this revision are hilighted in italics.
A more efficient method of incorporating linked content from the clipboard is to read only the content kind you’re pasting into your part. If the part that placed the content on the clipboard wrote promises for all content kinds it can provide, this method will result in the transfer of only one representation between the two parts.
The difficulty with this recipe is that your part has to hold the object IDs of the link and link source objects it clones until cloning is completed by calling EndClone. (You can convert a valid object ID into a reference during cloning, but if the ID is invalid, there’s no way to represent that as a reference.) Only then can you determine if the IDs are valid and turn them into storage unit references or internalized objects.
The method show below uses the routine MyCloneStrongReference described in the Data Interchange Basics document.
Copying a Single Embedded Frame at the Source or Destination of a Link
Suppose your part maintains a link source that consists of a single embedded frame. Or, suppose your part maintains a link destination where the content from the link is embedded. In either case, your part maintains the source or destination of the link, but the content comes from, or is, embedded as a separate part.
If the user selects such an embedded frame and copies it to the clipboard or drag and drop object, your part should follow the "Putting a Single Embedded Frame on the Clipboard" recipe in the Clipboard Recipes document. When your part adds the kODPropProxyContents property to the data transfer storage unit, the value (or values) you write should include a reference to a link or link source object you clone to the data transfer draft. In this way, if the embedded frame is copied or moved, the link source or destination will also be copied or moved if the part performing the paste or receiving the drop understands a value in the proxy content property.
When copying a single embedded frame at the source of a link, your part should also write a link spec, so additional destinations of the existing link source may be created. In this case, a part creating a new destination will not incorporate the proxy content on the clipboard or drag and drop object. When your CreateLink method is called to get the existing link, the proxy content available through the link won’t contain the link source.